6. 도커 개요4(레이어 구조 이론)

2.4. 컨테이너 레이어 구조

참고:

이 문서는 레이어 구조의 이론을 다룸. 실습 내용은 7. 도커 개요5 (레이어 구조 실습) 문서 참조

핵심 개념

컨테이너는 변경 사항의 집합이며, 이러한 변경 사항을 레이어(Layer) 라고 부름. 컨테이너 이미지는 전체 작업 흐름에 따라 만들어진 레이어를 하나로 모은 집합체임.

레이어 개념:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[컨테이너 이미지]
    │
    ├─ 레이어 3: hello.sh 추가
    │            (변경 사항: +hello.sh)
    │
    ├─ 레이어 2: 실행 권한 부여
    │            (변경 사항: chmod +x)
    │
    └─ 레이어 1: Ubuntu 22.04
                 (베이스 파일시스템)

특징:
→ 각 레이어는 파일 추가/삭제/수정의 변경 집합
→ 레이어를 차례로 적용하면 최종 파일시스템 생성
→ 레지스트리는 레이어 단위로 업로드/다운로드
용어 정리

레이어 (Layer): 파일시스템 변경 사항의 집합. 추가/삭제/수정된 파일들을 하나로 묶은 단위.

tar: Tape Archive의 약자. 여러 파일을 하나로 묶는 아카이브 형식.


2.4.1. 컨테이너 이미지의 레이어 구조

레이어란 무엇인가

레이어의 정의:

예제: myimage:v1 이미지의 레이어 구조

앞선 실습에서 ubuntu:22.04 베이스 이미지에 hello.sh 스크립트를 추가한 myimage:v1 이미지를 생성함. 이 과정에서 다음과 같은 레이어 구조가 생성됨:

myimage:v1 레이어 구조:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[최종 이미지: myimage:v1]
    │
    ├─ 레이어 N: ENTRYPOINT 메타데이터
    │            └─ 시작 명령: /hello.sh
    │
    ├─ 레이어 3: chmod 실행 결과
    │            └─ 변경: /hello.sh 실행 권한 추가
    │
    ├─ 레이어 2: hello.sh 파일 복사
    │            └─ 추가: /hello.sh 파일
    │
    └─ 레이어 1: ubuntu:22.04 베이스
                 └─ /bin, /lib, /usr 등 기본 파일시스템

┌────────────────────────────────────┐
│ 컨테이너 실행 시                      │
│ → 레이어 1부터 순차 적용              │
│ → 최종 루트 파일시스템 구성           │
└────────────────────────────────────┘

레이어 중첩과 최종 파일시스템

컨테이너를 실행하면 다음 과정을 거쳐 루트 파일시스템을 구성함:

레이어 중첩 프로세스:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[1단계] 텅 빈 파일시스템
    ↓
[2단계] 레이어 1 (ubuntu:22.04) 적용
    ├─ /bin/bash
    ├─ /lib/...
    └─ /usr/...
    ↓
[3단계] 레이어 2 (hello.sh 복사) 적용
    └─ /hello.sh 추가
    ↓
[4단계] 레이어 3 (chmod) 적용
    └─ /hello.sh 실행 권한 변경
    ↓
[최종] 컨테이너 루트 파일시스템
    ├─ /bin/bash (레이어 1)
    ├─ /lib/... (레이어 1)
    ├─ /usr/... (레이어 1)
    └─ /hello.sh (레이어 2+3, 실행 가능)

2.4.2. 컨테이너 이미지 내부 구조

이미지 규격

도커 이미지 규격:

이미지 데이터 파일 분류

docker save 명령어로 이미지를 tar 형식으로 추출하면 다음과 같은 파일 구조를 확인할 수 있음:

이미지 내부 파일 구조:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

dumpimage/
│
├─ manifest.json
│  └─ 역할: 이미지 구성 정보
│     └─ 레이어 순서, 설정 파일 위치 등
│
├─ repositories
│  └─ 역할: 이미지 이름과 태그 정보
│
└─ [해시값]/
    ├─ layer.tar
    │  └─ 역할: 루트 파일시스템 변경 사항
    │     └─ 실제 파일 내용 (압축된 형태)
    │
    ├─ json
    │  └─ 역할: 레이어 메타데이터
    │     └─ 부모 레이어, 생성 시간 등
    │
    └─ [레이어_ID].json
       └─ 역할: 실행 환경 설정
          ├─ 환경 변수 (ENV)
          ├─ 시작 명령어 (ENTRYPOINT/CMD)
          ├─ 작업 디렉터리 (WORKDIR)
          └─ 실행 사용자 (USER)

┌────────────────────────────────────┐
│ 핵심: tar 파일이 실제 레이어         │
│ → 컨테이너 루트 FS의 변경사항 포함   │
└────────────────────────────────────┘

파일 분류표:

파일/디렉터리 역할 내용
layer.tar 파일시스템 데이터 루트 FS의 변경 사항 (파일 추가/수정)
*.json 실행 환경 설정 ENV, ENTRYPOINT, CMD, WORKDIR 등
manifest.json 이미지 구성 정보 레이어 순서, 설정 위치
repositories 이름/태그 정보 이미지 이름 및 태그 매핑
VERSION 규격 버전 이미지 포맷 버전 (호환성)
용어 정리

manifest.json: 이미지 구성 정보를 담은 메타데이터 파일. 레이어 순서, 설정 파일 위치 등 포함.

docker save: 이미지를 tar 파일로 추출하는 명령어. 이미지 백업, 오프라인 전송에 사용됨.


2.4.3. 컨테이너 빌드와 레이어 구조

Dockerfile 명령어와 레이어의 관계

도커는 Dockerfile의 FROM 명령 다음에 있는 각 명령에 따라 베이스 이미지를 순서대로 변경하면서 빌드를 진행함. 이때 각 명령 실행 결과로 발생한 변경 사항을 레이어로 각각 겹쳐 쌓음.

Dockerfile과 레이어 생성:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[Dockerfile]                    [생성되는 레이어]
━━━━━━━━━━━━━━━━━━              ━━━━━━━━━━━━━━━━━━

FROM ubuntu:22.04          →    [ubuntu:22.04 레이어]
                                └─ 그대로 포함 (복사 X)
    ↓
COPY ./hello.sh /hello.sh  →    [레이어 2]
                                └─ 변경: +/hello.sh
    ↓
RUN chmod +x /hello.sh     →    [레이어 3]
                                └─ 변경: /hello.sh 권한
    ↓
ENTRYPOINT ["/hello.sh"]   →    [메타데이터]
                                └─ 실행 설정 (레이어 X)

┌────────────────────────────────────┐
│ 원칙:                               │
│ → FROM의 베이스 레이어는 그대로 포함 │
│ → RUN, COPY, ADD 등은 새 레이어 생성│
│ → CMD, ENTRYPOINT는 메타데이터만     │
└────────────────────────────────────┘

레이어 중첩 시각화

이미지 빌드 레이어 누적:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[단계 1] FROM ubuntu:22.04
┌─────────────────────────┐
│   ubuntu:22.04 레이어     │  ← 읽기 전용
│   /bin, /lib, /usr, ...  │
└─────────────────────────┘

[단계 2] COPY ./hello.sh /hello.sh
┌─────────────────────────┐
│   + /hello.sh           │  ← 새 레이어 (읽기 전용)
├─────────────────────────┤
│   ubuntu:22.04 레이어     │  ← 그대로 유지
└─────────────────────────┘

[단계 3] RUN chmod +x /hello.sh
┌─────────────────────────┐
│   hello.sh 권한 변경     │  ← 새 레이어 (읽기 전용)
├─────────────────────────┤
│   + /hello.sh           │
├─────────────────────────┤
│   ubuntu:22.04 레이어     │
└─────────────────────────┘

[최종] myimage:v1
→ 3개 레이어 + 메타데이터(ENTRYPOINT)

빌드 캐시 메커니즘

도커는 도커파일의 각 명령을 실행할 때마다 변경된 내용을 캐시하면서 빌드를 진행함.

빌드 캐시 활용:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[첫 번째 빌드]
FROM ubuntu:22.04          → 레이어 1 생성 + 캐시
COPY ./hello.sh /hello.sh  → 레이어 2 생성 + 캐시
RUN chmod +x /hello.sh     → 레이어 3 생성 + 캐시
빌드 시간: 30초

[두 번째 빌드 - 변경 없음]
FROM ubuntu:22.04          → 캐시 사용 (즉시)
COPY ./hello.sh /hello.sh  → 캐시 사용 (즉시)
RUN chmod +x /hello.sh     → 캐시 사용 (즉시)
빌드 시간: 0.5초

[세 번째 빌드 - hello.sh 수정]
FROM ubuntu:22.04          → 캐시 사용 (즉시)
COPY ./hello.sh /hello.sh  → 캐시 무효화 (재실행)
RUN chmod +x /hello.sh     → 재실행
빌드 시간: 5초

캐시 규칙:
→ 이전 빌드와 동일한 명령 + 동일한 컨텍스트 = 캐시 사용
→ 한 단계에서 캐시 무효화 → 이후 모든 단계 재실행

효율적인 Dockerfile 작성 팁

레이어 구조와 캐시를 고려한 최적화 원칙:

Dockerfile 최적화 전략:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[나쁜 예]
FROM ubuntu:22.04
COPY ./app /app              ← 자주 변경 (캐시 무효화)
RUN apt-get update           ← 매번 재실행
RUN apt-get install -y ...   ← 매번 재실행
RUN pip install -r req.txt   ← 매번 재실행

[좋은 예]
FROM ubuntu:22.04
RUN apt-get update && \      ← 거의 변경 안 됨
    apt-get install -y ...   ← 캐시 유지
COPY requirements.txt /tmp/  ← 의존성 파일만 먼저
RUN pip install -r /tmp/...  ← requirements 변경 시만 재실행
COPY ./app /app              ← 소스 변경 시만 재실행

원칙:
→ 변경 빈도가 낮은 작업을 앞에 배치
→ 변경 빈도가 높은 작업을 뒤에 배치
→ RUN 명령 통합으로 레이어 수 감소

2.4.4. 컨테이너 실행의 레이어 구조

읽기 전용 레이어 공유

동일한 이미지로 여러 컨테이너를 실행해도 데이터 중복 없이 각 컨테이너가 독립적으로 동작함. 이는 레이어 공유 메커니즘 덕분임.

다중 컨테이너 실행 시 레이어 공유:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[호스트 OS]
    │
    └─ [이미지 레이어 - 공유 저장소]
        ├─ 레이어 1: ubuntu:22.04  (읽기 전용)
        ├─ 레이어 2: hello.sh 추가  (읽기 전용)
        └─ 레이어 3: chmod 실행     (읽기 전용)
            │
            ├─────────────┬─────────────┐
            │             │             │
       [컨테이너 1]   [컨테이너 2]   [컨테이너 3]
            │             │             │
       [읽기/쓰기]    [읽기/쓰기]    [읽기/쓰기]
       레이어 (독립)  레이어 (독립)  레이어 (독립)

특징:
→ 이미지 레이어는 읽기 전용으로 공유
→ 각 컨테이너는 독립적인 읽기/쓰기 레이어 보유
→ 스토리지 효율성 극대화

Copy on Write (CoW) 방식

컨테이너는 읽기 전용 레이어를 변경할 수 없지만, 실제로는 파일을 수정할 수 있음. 이는 Copy on Write 메커니즘 덕분임.

CoW 동작 원리:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[상황 1] 파일 읽기
컨테이너 → /hello.sh 읽기 요청
    ↓
하위 레이어에서 파일 탐색
    ↓
레이어 2에서 /hello.sh 발견
    ↓
읽기 전용으로 반환

[상황 2] 새 파일 생성
컨테이너 → /app/log.txt 생성
    ↓
하위 레이어에 없음
    ↓
읽기/쓰기 레이어에 직접 생성

[상황 3] 기존 파일 수정 (CoW 발동)
컨테이너 → /hello.sh 수정 시도
    ↓
하위 레이어(읽기 전용)에 /hello.sh 존재
    ↓
/hello.sh를 읽기/쓰기 레이어로 복사
    ↓
복사본을 수정
    ↓
이후 읽기 시 상위 레이어(수정본) 우선 반환

┌────────────────────────────────────┐
│ 핵심:                               │
│ → 하위 레이어 파일은 절대 변경 안 됨 │
│ → 변경 시 상위로 복사 후 수정        │
│ → 다른 컨테이너에 영향 없음          │
└────────────────────────────────────┘
용어 정리

Copy on Write (CoW): 쓰기 시 복사 기법. 파일 수정 시에만 복사본 생성하여 원본을 보호함.

레이어 우선순위와 파일 가시성

레이어 중첩 시 파일 탐색 순서:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[컨테이너 파일시스템 뷰]

레이어 4 (읽기/쓰기)  ← 최우선
├─ /hello.sh (수정됨)  ✓ 이 버전 반환
└─ /log.txt

레이어 3 (읽기 전용)
└─ /hello.sh (원본)    ✗ 가려짐 (invisible)

레이어 2 (읽기 전용)
└─ /hello.sh (원본)    ✗ 가려짐

레이어 1 (읽기 전용)
└─ /bin/bash          ✓ 상위에 없으면 반환

원칙:
→ 상위 레이어 우선 (Top-down 탐색)
→ 동일 경로 파일은 최상위 레이어 버전 사용
→ 하위 레이어는 가려지지만 삭제되지 않음

컨테이너 삭제 시 데이터 소멸

컨테이너 생명주기와 데이터:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[컨테이너 실행]
docker run -d --name mycontainer myimage:v1
    ↓
읽기/쓰기 레이어 생성 (컨테이너 전용)
    ↓
컨테이너 작업 (파일 생성/수정)
    ↓
변경 사항은 읽기/쓰기 레이어에만 저장

[컨테이너 삭제]
docker rm mycontainer
    ↓
읽기/쓰기 레이어 삭제
    ↓
모든 변경 사항 소멸

[이미지 레이어]
→ 영향 없음 (읽기 전용 레이어 유지)
→ 동일 이미지로 다시 실행 가능

데이터 영속성 확보 방법:
→ 볼륨(Volume) 사용
→ 바인드 마운트(Bind Mount)
→ docker commit으로 새 이미지 생성
용어 정리

docker commit: 컨테이너의 변경 사항을 새 이미지로 저장. 읽기/쓰기 레이어를 이미지 레이어로 변환함.


2.4.5. 레이어 구조의 이미지와 루트 파일시스템 작성에 필요한 기술

스토리지 드라이버 개념

도커는 스토리지 드라이버 컴포넌트를 사용하여 레이어 중첩 및 CoW 방식을 제공함.

스토리지 드라이버 역할:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[도커 엔진]
    ↓
[스토리지 드라이버]
    │
    ├─ 레이어 저장 관리
    │  └─ 각 레이어를 호스트에 저장
    │
    ├─ 레이어 중첩
    │  └─ 여러 레이어를 하나로 병합
    │
    └─ CoW 구현
       └─ 파일 수정 시 복사 메커니즘
    ↓
[컨테이너 루트 FS]

주요 구현 방식:
→ overlay2 (권장)
→ btrfs
→ zfs
→ devicemapper (레거시)
용어 정리

스토리지 드라이버 (Storage Driver): 도커에서 레이어 저장/관리를 담당하는 컴포넌트. overlay2, btrfs, devicemapper 등이 있음.

overlay2 스토리지 드라이버

여러 리눅스 배포판에서 권장하는 스토리지 드라이버. 오버레이 파일시스템(overlayfs)을 활용함.

overlay2 동작 방식:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[이미지 레이어 저장]
각 layer.tar 압축 해제
    ↓
/var/lib/docker/overlay2/[해시]/diff/
    └─ 레이어 내용을 디렉터리로 저장

[컨테이너 실행 시]
여러 디렉터리를 오버레이 FS로 중첩
    ↓
컨테이너 루트 FS로 마운트

[예시]
레이어 1: /var/lib/docker/overlay2/abc123/diff/
레이어 2: /var/lib/docker/overlay2/def456/diff/
레이어 3: /var/lib/docker/overlay2/ghi789/diff/
읽기/쓰기: /var/lib/docker/overlay2/xyz/diff/
    ↓
오버레이 마운트
    ↓
/var/lib/docker/overlay2/xyz/merged/
    → 컨테이너가 보는 루트 파일시스템

오버레이 파일시스템 (overlayfs)

리눅스 커널 3.18부터 도입된 기술. 어떤 디렉터리를 다른 디렉터리와 중첩한 결과를 마운트할 수 있는 파일시스템.

overlayfs 구성 요소:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[마운트 명령어 구조]
mount -t overlay overlay \
  -o lowerdir=layer1:layer2:layer3,\
     upperdir=upper,\
     workdir=work \
  merged

lowerdir (하위 디렉터리)
├─ layer1 (최상위 읽기 전용)
├─ layer2 (중간 읽기 전용)
└─ layer3 (최하위 읽기 전용)
    └─ 이미지 레이어 역할

upperdir (상위 디렉터리)
└─ upper (읽기/쓰기 가능)
    └─ 컨테이너 변경사항 저장

workdir (작업 디렉터리)
└─ work (커널 내부 작업용)

merged (마운트 포인트)
└─ 모든 레이어 중첩 결과
    └─ 컨테이너가 보는 파일시스템

┌────────────────────────────────────┐
│ 특징:                               │
│ → lowerdir 디렉터리는 변경 불가      │
│ → upperdir에만 변경사항 기록         │
│ → merged는 투명한 뷰 제공            │
└────────────────────────────────────┘
용어 정리

overlay2: 도커 권장 스토리지 드라이버. 리눅스 overlayfs 기반.

overlayfs: 리눅스 커널의 유니온 파일시스템. 여러 디렉터리를 중첩하여 하나로 보여줌.

lowerdir / upperdir: lowerdir는 읽기 전용 하위 레이어들, upperdir는 읽기/쓰기 가능한 상위 레이어.

도커 외 컨테이너 런타임의 활용

overlayfs 채택 컨테이너 런타임:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[컨테이너 런타임]
    │
    ├─ Docker (docker)
    │  └─ overlay2 스토리지 드라이버
    │
    ├─ containerd
    │  └─ overlayfs snapshotter
    │
    └─ CRI-O
       └─ overlay 스토리지 드라이버

공통점:
→ 모두 overlayfs 활용
→ 레이어 중첩 방식 동일
→ CoW 메커니즘 제공
용어 정리

snapshotter: containerd에서 스토리지 드라이버 역할을 하는 컴포넌트. 이미지 레이어 관리를 담당함.


정리

컨테이너 레이어 구조 핵심 개념:

  1. 레이어는 변경 사항의 집합

    • 파일 추가/삭제/수정을 tar 형식으로 저장
    • Dockerfile의 각 명령이 새 레이어 생성
  2. 레이어 중첩으로 파일시스템 구성

    • 하위부터 순차 적용
    • 상위 레이어가 우선순위를 가짐
  3. 읽기 전용 레이어 공유

    • 이미지 레이어는 컨테이너 간 공유
    • 스토리지 효율성 극대화
  4. 읽기/쓰기 레이어 분리

    • 각 컨테이너는 독립적인 쓰기 레이어 보유
    • CoW로 하위 레이어 보호
  5. 오버레이 파일시스템 활용

    • overlay2 스토리지 드라이버
    • 리눅스 커널의 overlayfs 기능 사용

참고 자료

공식 문서:

심화 자료: